package dfferber.xstream;

/*
 * Copyright (C) 2004, 2005, 2006 Joe Walnes.
 * Copyright (C) 2006, 2007, 2008 XStream Committers.
 * All rights reserved.
 *
 * The software in this package is published under the terms of the BSD
 * style license a copy of which has been included with this distribution in
 * the LICENSE.txt file.
 * 
 * Created on 02. March 2006 by Joerg Schaible
 */

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import com.thoughtworks.xstream.converters.ConversionException;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.SingleValueConverter;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.reflection.ReflectionProvider;
import com.thoughtworks.xstream.converters.reflection.SerializationMethodInvoker;
import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.mapper.Mapper;

public abstract class AbstractPojoConverter implements Converter {
	protected final ReflectionProvider reflectionProvider;
    protected final PojoReflectionProvider pojoReflectionProvider;
    protected final Mapper mapper;
    protected transient SerializationMethodInvoker serializationMethodInvoker;

	public AbstractPojoConverter(Mapper mapper, PojoReflectionProvider pojoReflectionProvider, ReflectionProvider reflectionProvider) {
		this.mapper = mapper;
		this.reflectionProvider = reflectionProvider;
		this.pojoReflectionProvider = pojoReflectionProvider;
		serializationMethodInvoker = new SerializationMethodInvoker();
	}
	
	  private Object readResolve() {
	      serializationMethodInvoker = new SerializationMethodInvoker();
	      return this;
	  }

	// original
    public void marshal(Object original, final HierarchicalStreamWriter writer, final MarshallingContext context) {
        final Object source = serializationMethodInvoker.callWriteReplace(original);

        if (source.getClass() != original.getClass()) {
            String attributeName = mapper.aliasForSystemAttribute("resolves-to");
            if (attributeName != null) {
                writer.addAttribute(attributeName, mapper.serializedClass(source.getClass()));
            }
            context.convertAnother(source);
        } else {
            doMarshal(source, writer, context);
        }
    }

	protected void doMarshal(final Object source, final HierarchicalStreamWriter writer, final MarshallingContext context) {
		final Set<String> seenProperties = new HashSet<String>();

		// Attributes might be preferred to child elements ...
		PojoReflectionProvider.Visitor visitor = new PojoReflectionProvider.Visitor() {
			@Override
			public void visit(String propertyName, Class<?> propertyType, Class<?> pojoType, Object value, boolean writeAble)
					throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
				if (!mapper.shouldSerializeMember(pojoType, propertyName)) {
					return;
				}
				SingleValueConverter converter = mapper.getConverterFromItemType(propertyName, propertyType, pojoType);
				if (converter != null) {
					if (value != null) {
						if (seenProperties.contains(propertyName)) {
							throw new ConversionException("Cannot write property with name '" + propertyName
									+ "' twice as attribute for object of type " + source.getClass().getName());
						}
						final String str = converter.toString(value);
						if (str != null) {
							writer.addAttribute(mapper.aliasForAttribute(mapper.serializedMember(pojoType, propertyName)), str);
						}
					}
					// TODO: name is not enough, need also "definedIn"!
					seenProperties.add(propertyName);
				}
			}
		};
		pojoReflectionProvider.visitSerializableProperties(source, visitor);
		

      // Child elements not covered already processed as attributes ...
      pojoReflectionProvider.visitSerializableProperties(source, new PojoReflectionProvider.Visitor() {
      	public void visit(String propertyName, Class<?> propertyType, Class<?> defineIn, Object newObj,
					boolean writeAble) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
	      		if (! mapper.shouldSerializeMember(defineIn, propertyName)) {
						return;
					}	
              if (! seenProperties.contains(propertyName) && newObj != null) {
                  Mapper.ImplicitCollectionMapping mapping = mapper.getImplicitCollectionDefForFieldName(source.getClass(), propertyName);
                  if (mapping != null) {
                      if (mapping.getItemFieldName() != null) {
                          Collection<?> list = (Collection<?>) newObj;
                          for (Iterator<?> iter = list.iterator(); iter.hasNext();) {
                              Object obj = iter.next();
                              writeProperty(propertyName, obj == null ? mapper.serializedClass(null) : mapping.getItemFieldName(), mapping.getItemType(), defineIn, obj);
                          }
                      } else {
                          context.convertAnother(newObj);
                      }
                  } else {
                      writeProperty(propertyName, null, propertyType, defineIn, newObj);
                  }
              }
          }

          private void writeProperty(String propertyName, String aliasName, Class propertyType, Class definedIn, Object value) {
              ExtendedHierarchicalStreamWriterHelper.startNode(writer, aliasName != null ? aliasName : mapper.serializedMember(source.getClass(), propertyName), propertyType); 

              if (value != null) {
                  Class actualType = value.getClass();
                  Class defaultType = mapper.defaultImplementationOf(propertyType);
                  if (! actualType.equals(defaultType)) {
                      String serializedClassName = mapper.serializedClass(actualType);
                      if (!serializedClassName.equals(mapper.serializedClass(defaultType))) {
                          String attributeName = mapper.aliasForSystemAttribute("class");
                          if (attributeName != null) {
                              writer.addAttribute(attributeName, serializedClassName);
                          }
                      }
                  }
  
                  PropertyDescriptor descriptor = pojoReflectionProvider.getPropertyDescriptor(definedIn, propertyName);
                  marshallProperty(context, value, descriptor);
              }
              writer.endNode();
          }

      });
  }

	// renomeado
	protected void marshallProperty(final MarshallingContext context, Object value, PropertyDescriptor descriptor) {
		context.convertAnother(value, mapper.getLocalConverter(descriptor.getReadMethod().getDeclaringClass(), descriptor.getName()));
	}

  // original
  public Object unmarshal(final HierarchicalStreamReader reader, final UnmarshallingContext context) {
      Object result = instantiateNewInstance(reader, context);
      result = doUnmarshal(result, reader, context);
      return serializationMethodInvoker.callReadResolve(result);
  }

  // não implementado agora
  public Object doUnmarshal(final Object result, final HierarchicalStreamReader reader, final UnmarshallingContext context) {
	  
	  throw new UnsupportedOperationException("Unmarshaling not yet implemented");
	  
  }

  // será renomeado
  protected Object unmarshallField(final UnmarshallingContext context, final Object result, Class type, Field field) {
      return context.convertAnother(result, type, mapper.getLocalConverter(field.getDeclaringClass(), field.getName()));
  }


  // será estudado 
//  private Map writeValueToImplicitCollection(UnmarshallingContext context, Object value,
//      Map implicitCollections, Object result, String itemFieldName) {
//      String fieldName = mapper.getFieldNameForItemTypeAndName(
//          context.getRequiredType(), value != null ? value.getClass() : Mapper.Null.class,
//          itemFieldName);
//      if (fieldName != null) {
//          if (implicitCollections == null) {
//              implicitCollections = new HashMap(); // lazy instantiation
//          }
//          Collection collection = (Collection)implicitCollections.get(fieldName);
//          if (collection == null) {
//              Class fieldType = mapper.defaultImplementationOf(reflectionProvider
//                  .getFieldType(result, fieldName, null));
//              if (!Collection.class.isAssignableFrom(fieldType)) {
//                  throw new ObjectAccessException("Field "
//                      + fieldName
//                      + " of "
//                      + result.getClass().getName()
//                      + " is configured for an implicit Collection, but field is of type "
//                      + fieldType.getName());
//              }
//              if (pureJavaReflectionProvider == null) {
//                  pureJavaReflectionProvider = new PureJavaReflectionProvider();
//              }
//              collection = (Collection)pureJavaReflectionProvider.newInstance(fieldType);
//              reflectionProvider.writeField(result, fieldName, collection, null);
//              implicitCollections.put(fieldName, collection);
//          }
//          collection.add(value);
//      } else {
//          throw new ConversionException("Element "
//              + itemFieldName
//              + " of type "
//              + value.getClass().getName()
//              + " is not defined as field in type "
//              + result.getClass().getName());
//      }
//      return implicitCollections;
//  }

  protected Object instantiateNewInstance(HierarchicalStreamReader reader, UnmarshallingContext context) {
      String attributeName = mapper.aliasForSystemAttribute("resolves-to");
      String readResolveValue = attributeName == null ? null : reader.getAttribute(attributeName);
      Object currentObject = context.currentObject();
      if (currentObject != null) {
          return currentObject;
      } else if (readResolveValue != null) {
          return reflectionProvider.newInstance(mapper.realClass(readResolveValue));
      } else {
          return reflectionProvider.newInstance(context.getRequiredType());
      }
  }

/*  private static class SeenFields {

      private Set seen = new HashSet();

      public void add(Class definedInCls, String fieldName) {
          String uniqueKey = fieldName;
          if (definedInCls != null) {
              uniqueKey += " [" + definedInCls.getName() + "]";
          }
          if (seen.contains(uniqueKey)) {
              throw new DuplicateFieldException(uniqueKey);
          } else {
              seen.add(uniqueKey);
          }
      }

  }*/

/*  private Class determineType(HierarchicalStreamReader reader, boolean validField, Object result, String fieldName, Class definedInCls) {
      String classAttribute = HierarchicalStreams.readClassAttribute(reader, mapper);
      if (classAttribute != null) {
          return mapper.realClass(classAttribute);
      } else if (!validField) {
          Class itemType = mapper.getItemTypeForItemFieldName(result.getClass(), fieldName);
          if (itemType != null) {
              return itemType;
          } else {
              String originalNodeName = reader.getNodeName();
              if (definedInCls == null) {
                  for(definedInCls = result.getClass(); definedInCls != null; definedInCls = definedInCls.getSuperclass()) {
                      if (!mapper.shouldSerializeMember(definedInCls, originalNodeName)) {
                          return null;
                      }
                  }
              }
              return mapper.realClass(originalNodeName);
          }
      } else {
          return mapper.defaultImplementationOf(reflectionProvider.getFieldType(result, fieldName, definedInCls));
      }
  }*/
  
  /*public static class DuplicateFieldException extends ConversionException {
      public DuplicateFieldException(String msg) {
          super(msg);
          add("duplicate-field", msg);
      }
  }*/
}
